package sh.orm;

import lombok.extern.slf4j.Slf4j;
import sh.orm.annotation.*;
import sh.orm.convert.JdbcType;
import sh.orm.convert.TypeConvertor;
import sh.orm.convert.TypeConvertorRegistry;
import sh.orm.query.Condition;
import sh.orm.query.Pageable;
import sh.orm.query.QueryCondition;
import sh.orm.util.ClassUtil;
import sh.orm.util.NameUtil;
import sh.base.aop.util.ReflectUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * 生成sql的工程
 * 单例模式
 * @author lqq
 * @date 2019/1/22
 */
@Slf4j
public class SqlFactory {


    public static final String RIGHT_KUOHAO = ")";
    public static final String LEFT_KUOHAO = "(";
    public static final String DOUHAO = ",";
    public static final String NULL_STRING = "NULL";
    public static final String EQUAL = "=";
    public static final String DAN_YIN_HAO = "'";
    public static final String KONG_GE = " ";
    public static final String KEY_WORD_AND = "AND";
    public static final String KEY_WORD_WHERE = "WHERE";


    private static SqlFactory sqlFactory;

    private SqlFactory(){}

    public static SqlFactory getInstance(){
        if(sqlFactory != null){
            return sqlFactory;
        }else {
            synchronized (SqlFactory.class){
                if(sqlFactory == null){
                    sqlFactory = new SqlFactory();
                }
            }
        }
        return sqlFactory;
    }

    /**
     * 生成一个插入sql
     * @param entity 对应的Entity类
     * @return
     */
    public <T> String getInsertSql(T entity){
        StringBuilder sb = new StringBuilder();

        //
        StringBuilder columns = new StringBuilder();
        StringBuilder values = new StringBuilder();
        StringBuilder sql = new StringBuilder();
        StaticEntity staticEntity = StaticEntityHolder.getStaticEntity(ClassUtil.getRealEntityClass(entity.getClass()));
        sql.append("insert into ").append(staticEntity.getTableName());

        for(String column : staticEntity.getColumnFieldMap().keySet()){
        //    先不管数据类型，把字段名和属性名对应上
            columns.append(column).append(DOUHAO);

        }
        //删除最后一个逗号
        columns.deleteCharAt(columns.lastIndexOf(","));


        TypeConvertor typeConvertor ;

        Map<String,Object> columnValueMap = EntityUtil.getColumnValueMap(entity);
        for(Map.Entry<String,Object> entry : columnValueMap.entrySet()){
            if(entry.getValue() == null){
                values.append(NULL_STRING);
            }else{
                //通过类型转换器，转为数据库中对象的类型
                JdbcType jdbcType = EntityUtil.getJdbcType(entity.getClass(), entry.getKey());
                typeConvertor = TypeConvertorRegistry.getInstance().getTypeConvertor(entry.getValue().getClass()
                        ,jdbcType);
                String jdbcValue = typeConvertor.convertToJdbcValue(entry.getValue());
                values.append(DAN_YIN_HAO).append(jdbcValue).append(DAN_YIN_HAO);
            }
            values.append(DOUHAO) ;
        }
        values.deleteCharAt(values.lastIndexOf(","));


        sql.append(LEFT_KUOHAO).append(columns).append(RIGHT_KUOHAO)
                .append(" values ")
                .append(LEFT_KUOHAO).append(values).append(RIGHT_KUOHAO);

        log.info("生成的sql：" + sql.toString());

        return sql.toString();
    }

    public <T> String getQuerySql(Class<T> clazz,Long id){
        String sql = "select * from %s where id = %s";
        String tableName = StaticEntityHolder.getStaticEntity(clazz).getTableName();
        sql = String.format(sql,tableName,id);
        log.info("生成的查询sql ===========》 " + sql);
        return sql;
    }


    /**
     * 获取更新操作的sql
     * @param entity
     * @param <T>
     * @return
     */
    public <T> String getUpdateSql(T entity) {
        StringBuilder sql = new StringBuilder();
        Class<?> clazz = ClassUtil.getRealEntityClass(entity.getClass());
        String tableName = StaticEntityHolder.getStaticEntity(clazz).getTableName();
        sql.append("update ").append(tableName).append(" SET ");

        Map<String,Object> columnValueMap = EntityUtil.getColumnValueMap(entity);

        for(Map.Entry<String,Object> entry : columnValueMap.entrySet()){
            //id 和 空值不需要改变。
            if(entry.getValue() != null && !"id".equals(entry.getKey())){
                sql.append(entry.getKey()).append(EQUAL).append(DAN_YIN_HAO).append(entry.getValue()).append(DAN_YIN_HAO).append(DOUHAO);
            }
        }

        //删除最后一个逗号
        sql.deleteCharAt(sql.length() - 1);

        sql.append("where id = ").append(columnValueMap.get("id"));

        log.info("生成的update sql =========》 " + sql.toString());

        return sql.toString();
    }

    public String getDeleteSql(Class<?> clazz, Long id) {
        StringBuilder sql = new StringBuilder();
        String tableName = StaticEntityHolder.getStaticEntity(clazz).getTableName();
        sql.append("delete from ").append(tableName).append(" where id = ").append(DAN_YIN_HAO).append(id).append(DAN_YIN_HAO);
        log.info("生成的delete sql =======》" + sql.toString());
        return sql.toString();
    }

    public String getSelectSql(Class<?> clazz) {
        StringBuilder sql = new StringBuilder();
        String tableName = StaticEntityHolder.getStaticEntity(clazz).getTableName();
        sql.append("select * from ").append(tableName);
        log.info("生成的select sql =======》" + sql.toString());
        return sql.toString();
    }


    /**
     * 有分页参数的sql
     * @param clazz
     * @param queryCondition
     * @param pageable
     * @return
     */
    public String getSelectLimitSql(Class<?> clazz , QueryCondition queryCondition, Pageable pageable) {
        String sql = getSelectSql(clazz, queryCondition);
        return sql + String.format(" limit %d , %d",pageable.getPage(),pageable.getSize());
    }


    /**
     * 获取查询总数的sql
     * @param queryCondition
     * @return
     */
    public String getCountSql(Class<?> clazz ,QueryCondition queryCondition) {
        String wherePhase = getWherePhase(queryCondition);
        String tableName = StaticEntityHolder.getStaticEntity(clazz).getTableName();
        StringBuilder sb = new StringBuilder();
        sb.append("select count(*) from ").append(tableName);
        if(wherePhase.length() > 0){
            sb.append(KONG_GE).append(KEY_WORD_WHERE).append(KONG_GE).append(wherePhase);
        }
        log.info("生成的查询总数 sql =======》" + sb.toString());
        return sb.toString();
    }

    /**
     * 只返回where部分,不包括 "where" 关键字
     * 如：id = 1 and name = 2
     * @param queryCondition
     * @return
     */
    private String getWherePhase(QueryCondition queryCondition){
        StringBuilder conditionSB = new StringBuilder();
        List<Condition> conditionList = queryCondition.getConditionList();
        for(int i = 0 ; i < conditionList.size() ; i ++){
            conditionSB.append(conditionList.get(i).getColumn())
                    .append(conditionList.get(i).getCompare())
                    .append(DAN_YIN_HAO).append(conditionList.get(i).getArg()).append(DAN_YIN_HAO);

            // 不是最后一个条件都需要添加 AND
            if(i != conditionList.size() - 1){
                conditionSB.append(KONG_GE).append(KEY_WORD_AND).append(KONG_GE);
            }
        }
        return conditionSB.toString();
    }

    /**
     * 根据查询条件获取sql
     * @param clazz
     * @param queryCondition
     * @return
     */
    public String getSelectSql(Class<?> clazz , QueryCondition queryCondition) {
        StringBuilder sql = new StringBuilder();
        String tableName = StaticEntityHolder.getStaticEntity(clazz).getTableName();
        sql.append("select * from ").append(tableName);

        String conditionSB = getWherePhase(queryCondition);

        //如果有查询参数，把where条件带上
        if(conditionSB.length() > 0){
            sql.append(KONG_GE).append(KEY_WORD_WHERE).append(KONG_GE).append(conditionSB);
        }
        log.info("生成的select sql =======》" + sql.toString());
        return sql.toString();
    }





    /**
     * 获取关联查询的sql
     * @param method 反射的方法
     * @param proxyEntity 生成的代理对象，代理对象是已经赋值了的
     * @return
     */
    public String getQuerySql(Method method, Object proxyEntity) {

        Long id = ReflectUtil.getFieldValueWithAnnotation(proxyEntity, proxyEntity.getClass(), Id.class);

        //关联查询的注解
        Annotation annotation = StaticEntityHolder.getStaticEntity(method.getDeclaringClass()).getQueryMethodAnnotationMap().get(method.getName());
        if(annotation == null){
            throw new OrmException("没有扫描到关联的注解");
        }
        //关联的类得到关联的类得到表名称，
        Class<?> joinClass = null;

        // *对1的，返回的肯定是一个简单对象
        if(annotation instanceof ManyToOne || annotation instanceof OneToOne){
            joinClass = method.getReturnType();
        }
        // * 对多的，返回的肯定是一个List
        if(annotation instanceof ManyToMany || annotation instanceof OneToMany){
            joinClass = ReflectUtil.getListGenericClass(method.getGenericReturnType());
        }

        //关联的字段默认是属性加上下划线和id，如果有 @JoinColumn注解，则以@JoinColumn注解为准,这里暂时先不使用@JoinColumn字段
        String joinColumnStr = null;
        JoinColumn joinColumn = null;
        Annotation[] annotations = StaticEntityHolder.getStaticEntity(method.getDeclaringClass())
                .getFieldAnnotationsMap().get(NameUtil.getFieldByGetName(method.getName()));
        for(Annotation anno : annotations){
            if(anno instanceof JoinColumn){
                joinColumn = (JoinColumn)anno;
                break;
            }
        }
        if(joinColumn != null){
            joinColumnStr = NameUtil.convertHumpToUnderline(joinColumn.name());
        }else {
            joinColumnStr = NameUtil.convertHumpToUnderline(NameUtil.getFieldByGetName(method.getName())) + "_id";
        }

        StringBuilder sql = new StringBuilder();
        sql.append("SELECT * FROM ");
        String tableName = StaticEntityHolder.getStaticEntity(joinClass).getTableName();
        sql.append(tableName).append(" where ").append(NameUtil.convertHumpToUnderline(joinColumnStr)).append(" = ").append(id);
        log.info("生成的关联查询 sql =======》" + sql.toString());
        return sql.toString();
    }

}
